Understanding Reflection

In the .NET universe, reflection is the process of runtime type discovery. Using reflection services, you are able to programmatically obtain the same metadata information displayed by ildasm.exe using a friendly object model. For example, through reflection, you can obtain a list of all types contained within a given *.dll or *.exe assembly (or even within a *.netmodule file, if you have a multi-file assembly), including the methods, fields, properties, and events defined by a given type. You can also dynamically discover the set of interfaces supported by a given type, the parameters of a method, and other related details (base classes, namespace information, manifest data, and so forth).

Like any namespace, System.Reflection (which is defined in mscorlib.dll) contains a number of related types. Table 15-1 lists some of the core items you should be familiar with.

Table 15-1. A Sampling of Members of the System.Reflection Namespace

Type Meaning in Life
Assembly This abstract class contains a number of static methods that allow you to load, investigate, and manipulate an assembly.
AssemblyName This class allows you to discover numerous details behind an assembly’s identity (version information, culture information, and so forth).
EventInfo This abstract class holds information for a given event.
FieldInfo This abstract class holds information for a given field.
MemberInfo This is the abstract base class that defines common behaviors for the EventInfo, FieldInfo, MethodInfo, and PropertyInfo types.
MethodInfo This abstract class contains information for a given method.
Module This abstract class allows you to access a given module within a multi-file assembly.
ParameterInfo This class holds information for a given parameter.
PropertyInfo This abstract class holds information for a given property.

To understand how to leverage the System.Reflection namespace to programmatically read .NET metadata, you need to first come to terms with the System.Type class.

The System.Type Class

The System.Type class defines a number of members that can be used to examine a type’s metadata, a great number of which return types from the System.Reflection namespace. For example, Type.GetMethods() returns an array of MethodInfo objects, Type.GetFields() returns an array of FieldInfo objects, and so on. The complete set of members exposed by System.Type is quite expansive; however, Table 15-2 offers a partial snapshot of the members supported by System.Type (see the .NET Framework 4.0 SDK documentation for full details).

Type Meaning in Life
IsAbstract
IsArray
IsClass
IsCOMObject
IsEnum
IsGenericTypeDefinition
IsGenericParameter
IsInterface
IsPrimitive
IsNestedPrivate
IsNestedPublic
IsSealed
IsValueType
These properties (among others) allow you to discover a number of basic traits about the Type you are referring to (e.g., if it is an abstract entity, an array, a nested class, and so forth).
GetConstructors()
GetEvents()
GetFields()
GetInterfaces()
GetMembers()
GetMethods()
GetNestedTypes()
GetProperties()
These methods (among others) allow you to obtain an array representing the items (interface, method, property, etc.) you are interested in. Each method returns a related array (e.g., GetFields() returns a FieldInfo array, GetMethods() returns a MethodInfo array,etc.). Be aware that each of these methods has a singular form (e.g.,GetMethod(), GetProperty(), etc.) that allows you to retrieve a specific item by name, rather than an array of all related items.
FindMembers() This method returns a MemberInfo array based on search criteria.
GetType() This static method returns a Type instance given a string name.
InvokeMember() This method allows “late binding” for a given item. You’ll learn about late binding later in this chapter.

Obtaining a Type Reference Using System.Object.GetType()

You can obtain an instance of the Type class in a variety of ways. However, the one thing you cannot do is directly create a Type object using the new keyword, as Type is an abstract class. Regarding your first choice, recall that System.Object defines a method named GetType(), which returns an instance of the Type class that represents the metadata for the current object:

// Obtain type information using a SportsCar instance.
SportsCar sc = new SportsCar();
Type t = sc.GetType();

Obviously, this approach will only work if you have compile-time knowledge of the type you wish to reflect over (SportsCar in this case) and currently have an instance of the type in memory. Given this restriction, it should make sense that tools such as ildasm.exe do not obtain type information by directly calling System.Object.GetType() for each type, given the ildasm.exe was not compiled against your custom assemblies!

Obtaining a Type Reference Using typeof()

The next way to obtain type information is using the C# typeof operator:

// Get the Type using typeof.
Type t = typeof(SportsCar);

Unlike System.Object.GetType(), the typeof operator is helpful in that you do not need to first create an object instance to extract type information. However, your code base must still have compiletime knowledge of the type you are interested in examining, as typeof expects the strongly typed name of the type.

Obtaining a Type Reference Using System.Type.GetType()

To obtain type information in a more flexible manner, you may call the static GetType() member of the System.Type class and specify the fully qualified string name of the type you are interested in examining. Using this approach, you do not need to have compile-time knowledge of the type you are extracting metadata from, given that Type.GetType() takes an instance of the omnipresent System.String.

Note When I say you do not need compile-time knowledge when calling Type.GetType(), I am referring to the fact that this method can take any string value whatsoever (rather than a strongly typed variable). Of course, you would still need to know the name of the type in a “stringified” format!

The Type.GetType() method has been overloaded to allow you to specify two Boolean parameters, one of which controls whether an exception should be thrown if the type cannot be found, and the other of which establishes the case sensitivity of the string. To illustrate, ponder the following:

// Obtain type information using the static Type.GetType() method
// (don't throw an exception if SportsCar cannot be found and ignore case).
Type t = Type.GetType("CarLibrary.SportsCar", false, true);

In the previous example, notice that the string you are passing into GetType() makes no mention of the assembly containing the type. In this case, the assumption is that the type is defined within the currently executing assembly. However, when you wish to obtain metadata for a type within an external private assembly, the string parameter is formatted using the type’s fully qualified name, followed by a comma, followed by the friendly name of the assembly containing the type:

// Obtain type information for a type within an external assembly.
Type t = Type.GetType("CarLibrary.SportsCar, CarLibrary");

As well, do know that the string passed into Type.GetType() may specify a plus token (+) to denote a nested type. Assume you wish to obtain type information for an enumeration (SpyOptions) nested within a class named JamesBondCar. To do so, you would write the following:

// Obtain type information for a nested enumeration
// within the current assembly.
Type t = Type.GetType("CarLibrary.JamesBondCar+SpyOptions");